package edu.psu.geovista.app.pcp.plot.layer;

import edu.psu.geovista.app.pcp.toolkits.group.DataIDSet;
import edu.psu.geovista.app.plot.axis.AxisList;
import edu.psu.geovista.app.plot.axis.XAxis;
import edu.psu.geovista.app.plot.axis.XAxisOrder;
import edu.psu.geovista.app.plot.layer.Layer;
import edu.psu.geovista.common.utils.collection.CollectionUtils;
import edu.psu.geovista.data.model.AppDataModel;

import java.awt.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * All Rights Researved.
 *
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
 *
 * Description
 *
 * Date: May 29, 2003
 * Time: 10:30:00 PM
 * @author Jin Chen
 */

public class DataLayer extends ImageLayer {
    public static final int  Display_All=1;
    public static final int  Display_ShowRange=2;
    public static final int  Display_Selected=3;
    public static final float DataBackgroundLightness=0.25f;
    public static final float DataDefaultLightness=1.0f;

    public static final boolean DEBUG = false;

    //PcpPlot plot;
    DataPainter painter;
    //AppDataModel dataModel;

    Hashtable dataGroups=new Hashtable(4);// 

    //private int dataDisplayStrategy=Display_All;
    private Set frozenIds=new HashSet(0);  //ids of observation that being frozened
    private Stroke defaultStroke=new BasicStroke(1.0f);
    private Stroke hightLightStroke=new BasicStroke(2.5f);
    private Color highlightColor=Color.blue ;
    //private boolean eraseFrozen;


    public DataLayer(String name,AppDataModel dataModel, AxisList yaxisList, XAxis xaxis, Dimension size,Color[] colors) {

            if(dataModel.getRowCount() !=colors.length){  // 
               new IllegalArgumentException(this.getClass().getName() +" dataModel size != size of the data's colors");
            }

        //this.dataModel =dataModel;
        painter=new DataPainter(yaxisList,xaxis,dataModel);
        //this.dataModel =((PcpPlot)pcp).getDataModel();
        //axisLayer=pcp.getAxisLayer();
        this.xaxis=(XAxisOrder) xaxis;
        this.yaxisList=yaxisList;//.getAxisList() ;
        this.setSize(size );
        //this.setLevel(Layer.BELOW_MIDDLE) ;
        this.setName(name );
        this.setType(Layer.TYPE_DATA );
        if (colors!=null){
            this.setColors(colors);
        }
        initDataGroup(dataModel);
        this.paintLayer() ; //do first paint
    }

    private void initDataGroup(AppDataModel dataModel) {
        int[] ids=((AppDataModel)dataModel).getAllRowIndexs() ;
        DataIDSet showR=new DataIDSet(ids,DataIDSet.TYPE_AxisShowRangeData );
        DataIDSet ctg=new DataIDSet(ids,DataIDSet.TYPE_CategoryData );
        dataGroups.put(showR.getType() ,showR);
        dataGroups.put(ctg.getType() ,ctg);
    }

    public void paintComponent(Graphics g){

         painter.drawData(g);


         paintFrozenObservations(g);


    }

    private void paintFrozenObservations(Graphics g) {
         Graphics2D g2d = (Graphics2D) g;
        Set frozenIds = this.getFrozenIds();
        if(frozenIds !=null&&frozenIds.size() >0){
            int[] fids = CollectionUtils.convert2IntArray(frozenIds);
             Color old = g.getColor();
              g2d.setColor(this.highlightColor);//this.highlightColor);
           /* if(eraseFrozen){
                g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.XOR) );
            }*/
            for (int i=0;i<fids.length ;i++){
                painter.drawAHighlightRecord(g2d,fids[i],false);
            }
            g.setColor(old);

        }
    }

    /*public void paintHighLightRecord(Graphics g,String rowLabel,int recordId,int x,int y) {
        this.painter.drawAHighlightRecord((Graphics2D)g,recordId,0,this.yaxisList.size() -1);

        this.painter.drawHighlightRecordLabel((Graphics2D)g,rowLabel,x,y);
    }*/

    /**
     *  draw line to link all the axises between "from" axis and "to" axis
     */
    private  void linkAxis(int from, int to) {
        Graphics2D g2d = this.getImagePainter();
        this.painter.linkAxis(g2d, from, to);
        paintFrozenObservations(g2d);   // 
        g2d.dispose();


    }

    public void repaintAxis(int index) {
        Rectangle rFrm=this.getAxisCleanRegion(index,0);
        this.cleanRegoin(rFrm);
        int[] ni=this.getNeighborIndex(index); //neighborIndex
        this.linkAxis(ni[0],ni[1]); // 
        //this.paintLayer() ;
        this.fireLayerChangeEvent();
    }
    public void repaintAxis(int[] index) {
        Arrays.sort(index); //ascending order
        Rectangle rFrm=null;
        for (int i=0;i<index.length ;i++){
            int[] ni=this.getNeighborIndex(index[i]); //neighborIndex
            int as,ae;//start axis, end axis
            if (i==0||index[i]-index[i-1]!=1){
                rFrm=this.getAxisCleanRegion(index[i],0);//clean both side
                as=ni[0];
                ae=ni[1];
            }
            else{
                //the axis is next to the previous axis
                   rFrm=this.getAxisCleanRegion(index[i],1); //clean right side
                   as=index[i];
                   ae=ni[1];

            }
            this.cleanRegoin(rFrm);

            this.linkAxis(ni[0],ni[1]);

        }
         this.fireLayerChangeEvent();

    }

    /**
     * @param   index   the display index of the axis to be cleaned
     * @param   whichSide  <0: left side area of the given axis ; 0: both side;>0: right side
     * @return a regoin need be cleaned to redraw an axis
     *                  if (region<0): return left side area of the given axis
     *                  if (region==0): return both side area of the given axis
     */
    private Rectangle getAxisCleanRegion(int index,int whichSide){

        int[] ni=this.getNeighborIndex(index); //neighborIndex
        int xl= this.yaxisList.getAxis(ni[0]).getLocation().x ;//left axis
        int xm= this.yaxisList.getAxis(index).getLocation().x ;//middle axis(given one)
        int xr=this.yaxisList.getAxis(ni[1]).getLocation().x ;//right axis
        int width,height;
        int xs;//x value of start axis of the region
        if (whichSide==0){  //both side
         width=xr-xl;
         xs=xl;
        }
        else if (whichSide<0){//left
          width=xm-xl;
           xs=xl;
        }
        else {//>0,right
           width=xr-xm;
           xs=xm;
        }
        height=this.getSize().height ;
        return new Rectangle(xs,0,width,height);
    }
    /**
     * @return neighbor indexs of given index
     *         left of 0th index and right of last index are themself
     */
    private int[] getNeighborIndex(int index){
        int left;
        if (index!=0){
            left= (index-1) ;
        }
        else{//fisrt index
            left= index;
        }

        int right;
        int lastAxisIndex=yaxisList.size() -1;//this.dataModel.getNumDimensions()-1 ;
        if(index<lastAxisIndex){
            right= index+1 ;
        }
        else{
            right= lastAxisIndex ;
        }
        return new int[]{left,right};
    }



   /* public int getDataDisplayStrategy() {
        return dataDisplayStrategy;
    }

    public void setDataDisplayStrategy(int dataDisplayStrategy) {
        this.dataDisplayStrategy = dataDisplayStrategy;
    }*/


    /**
     *
     * @param recordsToShow  if null, means show all data within the show range and categories to show
     */
    public void setRecordsToShow(DataIDSet recordsToShow) {

        if (recordsToShow!=null){
            this.dataGroups.put(recordsToShow.getType() ,recordsToShow);
        }
        else{
            throw new IllegalArgumentException("Don't accept null, must use DataGroup!");
            //    dataGroups.remove(DataGroup.TYPE_SelectData);
        }
        this.updateRecordsToShow() ;
        //If recordsToShow==null, still do joinUnion. It will show data within the show range and categories to show
        //As selection is sessionless, it won't be considered if not specified

    }
    public void updateRecordsToShow() {
        int[] ids=joinUnionDataGroup();
        if(ids!=null){

            painter.setRecordsToShow(ids);
        }
        else{
            /**  
             *   
             *   
             */
            ids=new int[0];
             painter.setRecordsToShow(ids);
        }
    }

    private int[] joinUnionDataGroup(){
        DataIDSet showR=(DataIDSet) this.dataGroups.get(DataIDSet.TYPE_AxisShowRangeData );
        DataIDSet ctg=(DataIDSet) this.dataGroups.get(DataIDSet.TYPE_CategoryData );
        DataIDSet select= (DataIDSet) this.dataGroups.get(DataIDSet.TYPE_SelectData );
        //int[] result=this.joinUnionDataGroup(showR,ctg) ;
        int[] result=DataIDSet.joinUnionDataIDSet(showR,ctg) ;
        if (result!=null&&result.length >0){
        DataIDSet tmp=new DataIDSet(result,null);//

            try {
                result=DataIDSet.joinUnionDataIDSet(tmp,select);
                /*if(result!=null &&result.length >0){
                    System.out.println("found data");
                }*/
            } catch (NullPointerException e) {
                //if(DEBUG)
                    e.printStackTrace();
                //todo log
            }


        }
        else{
            if (select!=null)
                result=select.getIndice() ;
            else
                result=null;
        }
        /* 
             *Therefore, must delete current selection to protect against any interfere on next selection operation
             */
            //select=null;
         dataGroups.remove(DataIDSet.TYPE_SelectData);
        return result;
    }
    /**
     *
     * @param a
     * @param b
     * @return  join Union of two dataGroup. If one =null(e.g. Not set yet), it won't affect anything. Only return the other's ID.
     */
    private int[] joinUnionDataGroup(DataIDSet a, DataIDSet b){
        int[] result=null;
        if(a!=null&&b!=null){
            int[] aData=a.getIndice() ;
            int[] bData=b.getIndice() ;
            result= DataIDSet.getJoinUnion(aData,bData);
        }
        else if (a!=null&&b==null){
            result=a.getIndice() ;
        }
        else if (a==null&&b!=null){
             result=b.getIndice() ;
        }
        else{
            result=null;
        }
        return result;

    }

    public void setColors(Color[] colors) {
         painter.setColors(colors);
    }
    public boolean isInvalidDataShown() {
        return this.painter.isInvalidDataShown() ;
    }

    public void setInvalidDataShown(boolean invalidDataShown) {
        this.painter.setInvalidDataShown(invalidDataShown);
    }

    public void setDataModel(AppDataModel dataModel) {
        this.initDataGroup(dataModel);
        this.painter.setDataModel(dataModel);
    }
    /**
     * determine the width of line painted
     * @param h
     */
    public void setStrongStrokeMode(boolean h) {
        if(h){
            this.painter.setDefaultStroke(this.hightLightStroke );
            this.painter.setAntialising(true);
        }
        else{
             this.painter.setDefaultStroke(this.defaultStroke );
             this.painter.setAntialising(false);
        }
    }
    //temp solution for set string stroke
    public void setStroke(Stroke stroke){
        this.painter.setDefaultStroke(stroke);
        this.defaultStroke=stroke;
    }
    //temp for supporting set stroke
     public void setAConfigInfo(String name,Object value) {
        this.configInfo.setConfig(name,value);
        if(name.equals(ConfigInfo.Attr_STROKE ) ){
             Stroke stk=(Stroke) value;
             this.setStroke(stk);
             //this.repaintLayer();
        }
        else if(name.equals(ConfigInfo.Attr_AntiAlias )){
            Boolean antialias=(Boolean) value;
            this.painter.setAntialising(antialias.booleanValue() );
        }
        this.repaintLayer();
    }
    //temp <--
    public Set getFrozenIds() {
        return frozenIds;
    }

    public void setFrozenIds(Set frzIds) {

        this.frozenIds.clear();
        this.frozenIds.addAll(frzIds);
    }

    public void resetDataGroup() {
        this.dataGroups =new Hashtable(4);
    }
    //temp
    public DataPainter getPainter() {
        return painter;
    }


}
